﻿using System;
using System.Collections.Generic;
#if !SILVERLIGHT && !NETFX_CORE
using System.Collections.Concurrent;
#endif
using System.Diagnostics.Contracts;
#if SILVERLIGHT || NETFX_CORE
using System.Threading;
#endif
#if NETFX_CORE
using Windows.UI.Xaml.Controls;
using Windows.System;
using Windows.UI.Core;
using Windows.UI.Xaml.Input;
#else
using System.Windows.Input;
#endif

namespace DaveSexton.Labs
{
#if NETFX_CORE
	[CLSCompliant(false)]
	public abstract partial class WindowsStoreConsoleLab
#elif WINDOWS_PHONE
	public abstract partial class PhoneConsoleLab
#elif SILVERLIGHT
	public abstract partial class SilverlightConsoleLab
#else
	public abstract partial class WindowsConsoleLab
#endif
	{
		#region Public Properties
		#endregion

		#region Private / Protected
#if SILVERLIGHT || NETFX_CORE
#if NETFX_CORE
		private const VirtualKey enterKey = VirtualKey.Enter;
		private const VirtualKey shiftKey = VirtualKey.Shift;
		private const VirtualKey ctrlKey = VirtualKey.Control;
		private const VirtualKey altKey = VirtualKey.None;
#else
		private const Key enterKey = Key.Enter;
		private const Key shiftKey = Key.Shift;
#if SILVERLIGHT
		private const Key ctrlKey = Key.Ctrl;
#else
		private const Key ctrlKey = Key.Control;
#endif
		private const Key altKey = Key.Alt;
#endif
		private static readonly AutoResetEvent userSignal = new AutoResetEvent(false);
		private ConsoleKeyInfo lastUserKey;
		private string lastUserInput;
		private bool waitingForEnter;
#else
		private readonly ConcurrentQueue<InputAcquirer> inputAcquirers = new ConcurrentQueue<InputAcquirer>();
#endif
#if !WINDOWS_PHONE
		private char? lastTextInput;
#endif
		#endregion

		#region Methods
#if !SILVERLIGHT && !NETFX_CORE
		[ContractInvariantMethod]
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Required for code contracts.")]
		private void ObjectInvariant()
		{
			Contract.Invariant(inputAcquirers != null);
		}
#endif

#if SILVERLIGHT || NETFX_CORE
		private T AcquireInput<T>(Action prepare, Func<T> resultSelector)
		{
			Contract.Requires(resultSelector != null);
			EnsuresProxyUnmodified();

			userSignal.Reset();

#if NETFX_CORE
			Dispatcher.RunAsync(
				Windows.UI.Core.CoreDispatcherPriority.Normal,
				() =>
				{
					if (prepare != null)
					{
						prepare();
					}

					WaitingForUserInput = true;

					InputTextBox.Focus(Windows.UI.Xaml.FocusState.Programmatic);
				})
				.GetResultsSafe();
#else
			Dispatcher.BeginInvoke(() =>
				{
					if (prepare != null)
					{
						prepare();
					}

					WaitingForUserInput = true;

					InputTextBox.Focus();
				});
#endif

			userSignal.WaitOne();

			using (var valueRetrieved = new ManualResetEvent(false))
			{
				T result = default(T);

#if NETFX_CORE
				Dispatcher.RunAsync(
					Windows.UI.Core.CoreDispatcherPriority.Normal,
					() =>
					{
						result = resultSelector();

						valueRetrieved.Set();
					})
					.GetResultsSafe();
#else
				Dispatcher.BeginInvoke(() =>
					{
						result = resultSelector();

						valueRetrieved.Set();
					});
#endif

				valueRetrieved.WaitOne();

				return result;
			}
		}
#else
		private void PrepareAcquireInput(InputAcquirer input)
		{
			Contract.Requires(input != null);
			Contract.Requires(HasProxy);
			EnsuresProxyUnmodified();

			inputAcquirers.Enqueue(input);

			WaitingForUserInput = true;

			if (InputTextBox != null)
			{
				InputTextBox.Focus();
			}

			Contract.Assert(HasProxy);
		}

		private void InputAcquired<T>(InputAcquirer<T> input, T value)
		{
			Contract.Requires(input != null);

			Contract.Assume(!input.HasValue);

			input.Value = value;

			InputAcquirer unused;
			inputAcquirers.TryDequeue(out unused);

			if (inputAcquirers.Count == 0)
			{
				WaitingForUserInput = false;
			}

			InputTextBox.Text = string.Empty;
		}
#endif

		protected ConsoleKeyInfo PressAnyKeyToContinue()
		{
			Contract.Requires(HasProxy);
			EnsuresProxyUnmodified();

			TraceLine();
			TraceLine(Properties.Resources.PressAnyKeyToContinue);

			Contract.Assume(HasProxy);

			return WaitForKey();
		}

		protected ConsoleKeyInfo WaitForKey()
		{
			Contract.Requires(HasProxy);
			EnsuresProxyUnmodified();

#if SILVERLIGHT || NETFX_CORE
			return AcquireInput(null, () => lastUserKey);
#else
			using (var input = new InputAcquirer<ConsoleKeyInfo>())
			{
				Dispatcher.Invoke((Action<InputAcquirer>)PrepareAcquireInput, input);

				input.Acquire();

				Contract.Assert(HasProxy);

				return input.Value;
			}
#endif
		}

		protected string UserInput(string format, params object[] args)
		{
			Contract.Requires(HasProxy);
#if WINDOWS_PHONE_70
			Contract.Requires(!string.IsNullOrEmpty(format));
#else
			Contract.Requires(!string.IsNullOrWhiteSpace(format));
#endif
			Contract.Requires(args != null);
			EnsuresProxyUnmodified();
			Contract.Ensures(Contract.Result<string>() != null);

			Trace(format, args);

			Contract.Assume(HasProxy);

			return UserInput();
		}

		protected string UserInput()
		{
			Contract.Requires(HasProxy);
			EnsuresProxyUnmodified();
			Contract.Ensures(Contract.Result<string>() != null);

			string value;

#if SILVERLIGHT || NETFX_CORE
			value = AcquireInput(() => waitingForEnter = true, () => lastUserInput) ?? string.Empty;
#else
			using (var input = new InputAcquirer<string>())
			{
				Dispatcher.Invoke((Action<InputAcquirer>)PrepareAcquireInput, input);

				Contract.Assume(!input.HasValue);

				input.Acquire();

				Contract.Assert(HasProxy);

				value = input.Value ?? string.Empty;
			}
#endif

			TraceLine(value);

			return value;
		}

		protected IEnumerable<ConsoleKeyInfo> UserInputKeys(string format, params object[] args)
		{
#if WINDOWS_PHONE_70
			Contract.Requires(!string.IsNullOrEmpty(format));
#else
			Contract.Requires(!string.IsNullOrWhiteSpace(format));
#endif
			Contract.Requires(args != null);
			EnsuresProxyUnmodified();
			Contract.Ensures(Contract.Result<IEnumerable<ConsoleKeyInfo>>() != null);

			return UserInputKeysIterator(format, args);
		}

		private IEnumerable<ConsoleKeyInfo> UserInputKeysIterator(string format, params object[] args)
		{
			while (true)
			{
				yield return UserInputKey(format, args);
			}
		}

		protected ConsoleKeyInfo UserInputKey(string format, params object[] args)
		{
			Contract.Requires(HasProxy);
#if WINDOWS_PHONE_70
			Contract.Requires(!string.IsNullOrEmpty(format));
#else
			Contract.Requires(!string.IsNullOrWhiteSpace(format));
#endif
			Contract.Requires(args != null);
			EnsuresProxyUnmodified();

			Trace(format, args);

			Contract.Assume(HasProxy);

			return UserInputKey();
		}

		protected ConsoleKeyInfo UserInputKey()
		{
			var key = WaitForKey();

			TraceLine(key.KeyChar);

			return key;
		}

		protected Uri UserInputUrl(string format, params object[] args)
		{
			Contract.Requires(HasProxy);
#if WINDOWS_PHONE_70
			Contract.Requires(!string.IsNullOrEmpty(format));
#else
			Contract.Requires(!string.IsNullOrWhiteSpace(format));
#endif
			Contract.Requires(args != null);
			EnsuresProxyUnmodified();
			Contract.Ensures(Contract.Result<Uri>() != null);

			return UserInputUrl(UriKind.RelativeOrAbsolute, format, args);
		}

		protected Uri UserInputUrl(UriKind kind, string format, params object[] args)
		{
			Contract.Requires(HasProxy);
#if WINDOWS_PHONE_70
			Contract.Requires(!string.IsNullOrEmpty(format));
#else
			Contract.Requires(!string.IsNullOrWhiteSpace(format));
#endif
			Contract.Requires(args != null);
			EnsuresProxyUnmodified();
			Contract.Ensures(Contract.Result<Uri>() != null);
			Contract.Ensures(kind != UriKind.Absolute || Contract.Result<Uri>().IsAbsoluteUri);

			Uri url;
			while (!Uri.TryCreate(UserInput(format, args), kind, out url))
			{
				TraceLine();
				TraceError(Properties.Resources.InvalidUrl);
				TraceLine();

				Contract.Assume(HasProxy);
			}

			Contract.Assume(kind != UriKind.Absolute || url.IsAbsoluteUri);

			return url;
		}

#if SILVERLIGHT || NETFX_CORE
#if NETFX_CORE
		private bool KeyPressed(VirtualKey key, VirtualKeyModifiers mods, char? ignore = null)
#else
		private bool KeyPressed(Key key, ModifierKeys mods, char? ignore = null)
#endif
		{
			EnsuresProxyUnmodified();

			if (waitingForEnter)
			{
				if (key != enterKey || ignore == '\r')
				{
					return true;
				}

				lastUserInput = InputTextBox.Text;

				waitingForEnter = false;
			}
			else
			{
				var result = ConsoleKeyConverter.FromKey(key, mods);

				if (!result.HasValue || ignore == result.Value.KeyChar)
				{
					return false;
				}

				lastUserKey = result.Value;
			}

			InputTextBox.Text = string.Empty;

			WaitingForUserInput = false;

			userSignal.Set();

			return true;
		}

#if NETFX_CORE
		private void KeyPressed(char key, VirtualKeyModifiers mods)
#else
		private void KeyPressed(char key, ModifierKeys mods)
#endif
		{
			lastUserKey = ConsoleKeyConverter.FromChar(key, mods);

			InputTextBox.Text = string.Empty;

			WaitingForUserInput = false;

			userSignal.Set();
		}
#else
		private void KeyPressed(Key key, ModifierKeys mods, char? ignore)
		{
			EnsuresProxyUnmodified();

			InputAcquirer input;

			if (inputAcquirers.TryPeek(out input))
			{
				Contract.Assume(input != null);

				var lineInput = input as InputAcquirer<string>;

				if (lineInput != null)
				{
					if (key != Key.Enter || ignore == '\r')
					{
						return;
					}

					InputAcquired(lineInput, InputTextBox.Text);
				}
				else
				{
					var result = ConsoleKeyConverter.FromKey(key, mods);

					if (!result.HasValue || ignore == result.Value.KeyChar)
					{
						return;
					}

					InputAcquired((InputAcquirer<ConsoleKeyInfo>)input, result.Value);
				}
			}
		}

		private void KeyPressed(char key, ModifierKeys mods, InputAcquirer<ConsoleKeyInfo> input)
		{
			Contract.Requires(input != null);
			EnsuresProxyUnmodified();

			Contract.Assume(!input.HasValue);

			InputAcquired(input, ConsoleKeyConverter.FromChar(key, mods));
		}
#endif

#if NETFX_CORE
		private static VirtualKeyModifiers GetCurrentModifierKeys()
		{
			var window = CoreWindow.GetForCurrentThread();

			Contract.Assume(window != null);

			if (window.GetKeyState(VirtualKey.Control) != CoreVirtualKeyStates.None
				|| window.GetKeyState(VirtualKey.LeftControl) != CoreVirtualKeyStates.None
				|| window.GetKeyState(VirtualKey.RightControl) != CoreVirtualKeyStates.None)
			{
				return VirtualKeyModifiers.Control;
			}
			else if (window.GetKeyState(VirtualKey.Shift) != CoreVirtualKeyStates.None
				|| window.GetKeyState(VirtualKey.LeftShift) != CoreVirtualKeyStates.None
				|| window.GetKeyState(VirtualKey.RightShift) != CoreVirtualKeyStates.None)
			{
				return VirtualKeyModifiers.Shift;
			}
			else
			{
				return VirtualKeyModifiers.None;
			}
		}
#else
		private static ModifierKeys GetCurrentModifierKeys()
		{
			return Keyboard.Modifiers;
		}
#endif
		#endregion

		#region Event Handlers
#if NETFX_CORE
		internal void InputTextBox_KeyUp(object sender, KeyRoutedEventArgs e)
#else
		private void InputTextBox_KeyUp(object sender, KeyEventArgs e)
#endif
		{
			EnsuresProxyUnmodified();

			////System.Diagnostics.Debug.WriteLine("InputTextBox_KeyUp: {0}", e.Key);

#if SILVERLIGHT || NETFX_CORE
			if (WaitingForUserInput
				&& e.Key != shiftKey
				&& e.Key != altKey
				&& e.Key != ctrlKey)
			{
#if WINDOWS_PHONE
				var mods = GetCurrentModifierKeys();

				if (!KeyPressed(e.Key, mods))
				{
					KeyPressed((char) e.PlatformKeyCode, mods);
				}
#else

				KeyPressed(e.Key, GetCurrentModifierKeys(), ignore: lastTextInput);
#endif
			}
#else
			if (WaitingForUserInput
				&& e.Key != Key.LeftShift
				&& e.Key != Key.RightShift
				&& e.Key != Key.LeftAlt
				&& e.Key != Key.RightAlt
				&& e.Key != Key.LeftCtrl
				&& e.Key != Key.RightCtrl)
			{
				KeyPressed(e.Key, GetCurrentModifierKeys(), ignore: lastTextInput);
			}
#endif

#if !WINDOWS_PHONE
			lastTextInput = null;
#endif
		}

#if !WINDOWS_PHONE
#if NETFX_CORE
		internal void InputTextBox_TextChanged(object sender, TextChangedEventArgs e)
#else
		private void InputTextBox_TextInput(object sender, TextCompositionEventArgs e)
#endif
		{
			EnsuresProxyUnmodified();

			////System.Diagnostics.Debug.WriteLine("InputTextBox_TextInput: {0}", new[] { e.Text });

#if SILVERLIGHT || NETFX_CORE
			if (WaitingForUserInput && !waitingForEnter)
			{
#if NETFX_CORE
				var text = ((TextBox) sender).Text;
#else
				var text = e.Text;
#endif

				if (!string.IsNullOrEmpty(text))
				{
					char c;
					lastTextInput = c = text[0];

					KeyPressed(c, GetCurrentModifierKeys());
				}
			}
#else
			if (WaitingForUserInput)
			{
				InputAcquirer input;

				if (inputAcquirers.TryPeek(out input))
				{
					var keyInput = input as InputAcquirer<ConsoleKeyInfo>;

					if (keyInput != null)
					{
						var text = e.Text;

						if (!string.IsNullOrEmpty(text))
						{
							char c;
							lastTextInput = c = text[0];

							KeyPressed(c, GetCurrentModifierKeys(), keyInput);
						}
					}
				}
			}
#endif
		}
#endif
		#endregion
	}
}